/* Copyright © 2006 Sarp Arda Coskun.
* Contact e-mail: sacoskun@gmail.com

* This file is part of simpleP2P.

* simpleP2P is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License

* simpleP2P is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with AndPishti.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
 * P2P Client Program
 * Client program of the P2P network application.
 * Search ability.
 * Downloading in parts ability.
 * 
 * Author: Sarp Arda Coskun - 20201238
 * Date: 18 October 2006 with Eclipse 3.1
 * Version: 0.1 (Beta)
 * Tested on : 20 October 2006 with SuSE 10.0 Linux
 */

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.StringTokenizer;

public class P2PClient
{
	// Properties
	private static boolean isConnected;
	private static String commandInput, serverResponse, serverCommand;
	private static Socket clientSocket;				// Port connects to the server.
	private static Socket clientToClientSocket;		// Port connects to the server(other client).

	private static BufferedReader inFromUser;		// Buffers input from client.
	private static BufferedReader inFromServer;		// Buffers input from server.
	private static DataOutputStream outToServer;	// Streams out to the server.
	private static ServerSocket welcomeSocket;		// Port listens any connection request.

	private static String tempUserName = "";
	private static String tempIpAddress = "";
	private static String localPort = "";
	private static ArrayList filesSearched;
	private static String serverIp = "127.0.0.1";
	private static int serverPort = 6789;
	
	public static void main(String[] args) throws Exception
	{	
		int menuInput = 0;
		serverIp = args[0];
		serverPort = Integer.parseInt(args[1]);
		localPort = args[2];
		
		isConnected = false;
		inFromUser = new BufferedReader(new InputStreamReader(System.in));
		filesSearched = new ArrayList();
		
		System.out.println("Welcome to the P2P network example.");
		while(true)
		{
			if(!isConnected)
			{
				System.out.println("1 - Connect to the server.");		
			}
			else
			{
				System.out.println("2 - Search for a file on the network.");
				System.out.println("3 - Get the file from user(s).");
				System.out.println("4 - Exit from the network.");
			}

			try
			{
				// Gets menu number input from user.
				menuInput = Integer.parseInt(inFromUser.readLine());
			}
			catch (NumberFormatException e)
			{
				menuInput = -1;
			}

			if(!isConnected && menuInput != 1)
			{
				System.out.println("Warning: You need to connect to the server first.");
			}
	
			else if(isConnected && menuInput == 1)
			{
				System.out.println("Warning: You are already connected to the server.");
			}
			else
			{
				switch(menuInput)
				{
				case 1:
					if(connect())
					{
						System.out.println("You have successfully connected to the server.");
						
						// Listens any client from a new thread.
						System.out.println("Client is waiting for a client");
						ClientWelcomer myClientDenemeWelcomer;
						myClientDenemeWelcomer = new ClientWelcomer(welcomeSocket);
						Thread t = new Thread(myClientDenemeWelcomer);
						t.start();
						
					}
					else
					{
						System.out.println("You could not be able to connect to the server.");
					}
				break;
				case 2:
						System.out.println(search() + " files found on the server.");	
					break;
				case 3:
					if(get()) 
					{
						System.out.println("File received.");
					}
					else
					{
						System.out.println("File not found!");					
					}
					break;
				case 4:
					if(exit())
					{
						System.out.println("You have been successfully disconnected from the network.");						
					}
					else
					{
						System.out.println("You did not disconnect from the network.");						
					}
					break;
				default:
						System.out.println("Incorrect menu number entry.");						
					break;
					
				}
			}
		}
	}
	
	// Connects to the server
	private static boolean connect() throws IOException
	{		
		System.out.println("Connecting to the server. \n Usage: connect <user_name> <port_number>");
		
		// Gets and checks user command input.
		commandInput = inFromUser.readLine();
		StringTokenizer commandInputST = new StringTokenizer(commandInput);
		if( commandInputST.countTokens() != 3)
		{
			System.out.println("Wrong Command! Usage: connect <user_name> <port_number>");
			return false;
		}
		else
		{
			commandInputST.nextToken();
			tempUserName = commandInputST.nextToken();
			localPort = commandInputST.nextToken();	// port number equal to the local port
			serverCommand = "NEW CONNECTION " + tempUserName + " " + localPort + " FILES " + getFileNames();
		}
		
		clientSocket = new Socket(InetAddress.getByName(serverIp), serverPort);
		outToServer = new DataOutputStream(clientSocket.getOutputStream());
		inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		
		System.out.println("Sending connection request to the server...");
		outToServer.writeBytes(serverCommand + '\n');

		System.out.println("Waiting for a reply from server...");
		serverResponse = inFromServer.readLine();
		StringTokenizer serverResponseST = new StringTokenizer(serverResponse);
		
		if(serverResponseST.nextToken().equalsIgnoreCase("connected"))
		{
			tempUserName = serverResponseST.nextToken();
			tempIpAddress = serverResponseST.nextToken();
			System.out.println("IP:" + tempIpAddress);
			localPort = serverResponseST.nextToken();
			isConnected = true;
			welcomeSocket = new ServerSocket(Integer.parseInt(localPort));
			return true;
		}
		else
		{
			System.out.println("Error:" + serverResponse);
			isConnected = false;
			return false;
		}
	}
	
	// Returns the file names and their lengths(in bytes).
	private static String getFileNames()
	{
		String fileNames = "";
		
		File f = new File("shared");

		File files[] = f.listFiles();

		for(int i=0;i<files.length;i++)
		{
			if(files[i].isFile())
			{
				fileNames += files[i].getName() + " ";
				fileNames += files[i].length() + " ";
			}
		}
		return fileNames;
	}
	
	// Search for a file on the network. Does not show the user's own files.
	private static int search() throws IOException
	{		
		System.out.println("Searching for a file on the network. \n Usage: search <file_name>");

		// Gets and checks user command input.
		commandInput = inFromUser.readLine();
		StringTokenizer commandInputST = new StringTokenizer(commandInput);
		
		if(commandInputST.countTokens() != 2)
		{
			System.out.println("Wrong Command! Usage: search <file_name>");
			return 0;
		}
		else
		{			
			commandInputST.nextToken();	// search keyword
			String fileNameSearched = commandInputST.nextToken();
			return searchFile(fileNameSearched);
		}
	}
	private static int searchFile(String fileNameSearched) throws IOException
	{
		clientSocket = new Socket(InetAddress.getByName(serverIp), serverPort);
		outToServer = new DataOutputStream(clientSocket.getOutputStream());
		inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		int numberOfFilesFound = 0;

		System.out.println("Sending search file request to the server...");
		outToServer.writeBytes("SEARCH " + fileNameSearched + "\n");
		
		System.out.println("Waiting for a reply from server...");

		// Clears old search results from the list.
		if(!filesSearched.isEmpty())
		{
			filesSearched.clear();
		}
		
		while(true)
		{
			serverResponse = inFromServer.readLine();

			if(serverResponse.equals(fileNameSearched + " NOT FOUND"))
			{
				System.out.println("There are no files that starts with \"" + fileNameSearched + "\".");
				return 0;
			}
			
			else if(serverResponse.equals("."))
			{
				break;
			}
			else
			{
				StringTokenizer serverResponseST = new StringTokenizer(serverResponse);
				if(serverResponseST.countTokens() > 3)
				{
					String userName = serverResponseST.nextToken();
					String ipAddress = serverResponseST.nextToken();	// ip address
					int portNumber = Integer.parseInt(serverResponseST.nextToken());	// port number
					String fileName = serverResponseST.nextToken();
					int fileSize = Integer.parseInt(serverResponseST.nextToken());
					// Do not add the file that exists on client's own hard drive.
					if(!userName.equals(tempUserName))
					{
						System.out.println(userName + " " + fileName + " " + fileSize);
						filesSearched.add(new FileSearched(userName, ipAddress, portNumber, fileName, fileSize));
						numberOfFilesFound++;
					}
				}
			}
		}
		return numberOfFilesFound;
	}
	// Downloads the file from network
	private static boolean get() throws IOException
	{
		System.out.println("Getting the file from user(s).");
		System.out.println("Usage: get <file_name> <user_name> or get -p <file_name> (parts from different user)");				
		
		// Gets and checks user command input.
		commandInput = inFromUser.readLine();
		StringTokenizer commandInputST = new StringTokenizer(commandInput);

		
		if(commandInputST.countTokens() != 3)
		{
			System.out.println("Wrong Command! Usage: get <file_name> <user_name> or get -p <file_name>");			
		}
		else
		{
			commandInputST.nextToken();	// get keyword
			String fileNameToDownload = commandInputST.nextToken();
			String userNameDownloadFrom = commandInputST.nextToken();
			
			// Gets file from user(s) part by part.
			if(fileNameToDownload.equals("-p"))
			{
				// Breaks if there is no file with given name.
				fileNameToDownload = userNameDownloadFrom;
				if(searchFile(fileNameToDownload) == 0)
				{
					return false;
				}
				
				int fileSizeToDownload = ((FileSearched)filesSearched.get(0)).getFileSize();
				int noOfPartsToDivide = filesSearched.size();
				int remainder = fileSizeToDownload % noOfPartsToDivide;
				int fileSizeToDownaloadFromEachClient = (fileSizeToDownload - remainder) / noOfPartsToDivide;					
				File fileReceived = new File("shared" + File.separator + fileNameToDownload);
				FileWriter fileWriterReceived = new FileWriter(fileReceived);
				ArrayList threadArray = new ArrayList();
				
				
				for( int i = 0; i < filesSearched.size(); i++)
				{
					ClientFilePartThread myClientDenemeFilePartThread;
					myClientDenemeFilePartThread = new ClientFilePartThread(i, filesSearched, fileSizeToDownaloadFromEachClient, remainder);
					Thread t = new Thread(myClientDenemeFilePartThread);
					t.start();
					threadArray.add(t);
					
					if(i == filesSearched.size() - 1)
					{
					}
				}
				System.out.println("Parts of file are being downloaded...");
				for(int i = 0; i < threadArray.size(); i++)
				{
					while(((Thread)(threadArray.get(i))).isAlive())
					{
						// Waits the last thread to finish.
					}
				}
				
				String tempFileName = "";
				String byteArray = "";
				for( int i = 0; i < filesSearched.size(); i++)
				{
					tempFileName = Part.getName(fileNameToDownload, i);

					File fileToCombine = new File("shared" + File.separator + tempFileName );
					FileReader fileReaderToCombine = new FileReader(fileToCombine);
					int currentByte = fileReaderToCombine.read();

					
					while(currentByte != -1)
					{
						byteArray += currentByte + " ";
						currentByte = fileReaderToCombine.read();
					}
					fileReaderToCombine.close();					
					fileToCombine.delete();
				}				
				byteArray = byteArray.substring(0, byteArray.length() - 1);	// Deletes the last " " space.
				StringTokenizer byteArrayST = new StringTokenizer(byteArray);
				while(byteArrayST.hasMoreTokens())
				{
					fileWriterReceived.write(Integer.parseInt(byteArrayST.nextToken()));
				}

				fileWriterReceived.close();
				
				// Updates file list on the server.
				clientSocket = new Socket(InetAddress.getByName(serverIp), serverPort);
				outToServer = new DataOutputStream(clientSocket.getOutputStream());
				inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				
				System.out.println("Sending search file request to the server...");
				outToServer.writeBytes("UPDATE FILES " + fileNameToDownload + " " + fileSizeToDownload + " " + tempUserName + "\n");
				
			}
			else
			{
				// Breaks if there is no file with given name.
				if(searchFile(fileNameToDownload) == 0)
				{
					return false;
				}
				
				int fileSizeToDownload = ((FileSearched)filesSearched.get(0)).getFileSize();
				
				// Gets file from one user directly.
				for( int i = 0; i < filesSearched.size(); i++)
				{
					FileSearched myFileSearched = (FileSearched)(filesSearched.get(i));
					if(myFileSearched.getUserName().equals(userNameDownloadFrom) && myFileSearched.getFileName().equals(fileNameToDownload))
					{
						// Sends upload request to the client.
						clientToClientSocket = new Socket(InetAddress.getByName(myFileSearched.getIpAddress()), myFileSearched.getPortNumber());
						outToServer = new DataOutputStream(clientToClientSocket.getOutputStream());
						inFromServer = new BufferedReader(new InputStreamReader(clientToClientSocket.getInputStream()));
						System.out.println("Sending send file request to the server(other client)...");
						outToServer.writeBytes("SEND " + myFileSearched.getFileName() + "\n");
						System.out.println("Waiting for a reply from server(other client)...");						

						// Starts to download the file.
						serverResponse = inFromServer.readLine();	// FILE and fileName
						serverResponse = inFromServer.readLine();	// Empty line \n
						String fileInBytes = inFromServer.readLine();	// File in bytes.
						File fileReceived = new File("shared" + File.separator + fileNameToDownload);
						FileWriter fileWriterReceived = new FileWriter(fileReceived);
						StringTokenizer fileInBytesST = new StringTokenizer(fileInBytes);
						System.out.println(fileInBytes);
							
						while(fileInBytesST.hasMoreTokens())
						{
							fileWriterReceived.write(Integer.parseInt(fileInBytesST.nextToken()));
						}

						// Closes file writer.
						fileWriterReceived.close();
						
						// Updates file list on the server.
						clientSocket = new Socket(InetAddress.getByName(serverIp), serverPort);
						outToServer = new DataOutputStream(clientSocket.getOutputStream());
						inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

						System.out.println("Sending search file request to the server...");
						outToServer.writeBytes("UPDATE FILES " + fileNameToDownload + " " + fileSizeToDownload + " " + tempUserName + "\n");						

					}
					else
					{
						// No file found with given name.
						return false;
					}
				}
			}
		}
		return true;
	}
	
	// Disconnects from the network
	private static boolean exit() throws IOException
	{
		System.out.println("Are you sure to exit? (y)es - (n)o");					
		if(inFromUser.readLine().equalsIgnoreCase("y"))
		{

			clientSocket = new Socket(InetAddress.getByName(serverIp), serverPort);
			outToServer = new DataOutputStream(clientSocket.getOutputStream());
			inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
			
			
			System.out.println("Sending disconnect request to the server...");
			outToServer.writeBytes("DISCONNECT " + tempUserName + "\n");
			
			System.out.println("Waiting for a reply from server...");
			serverResponse = inFromServer.readLine();
			
			if(serverResponse.equalsIgnoreCase("disconnected"))
			{
				// Exits from the network.
				isConnected = false;
				clientSocket.close();
				return true;
			}
			else
			{
				// Cannot exit from the network.
				isConnected = true;
				return false;
			}
		}
		else
		{
			return false;
		}
	}	
}
